package com.easyaop.api.support.loadclass;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;

public class Java8ClassDefiner extends AbstractClassDefiner {
    private static final MethodHandles.Lookup IMPL_LOOKUP;

    private static final Method DEFINE_ANONYMOUS_CLASS_METHOD;

    private static final Method ENSURE_CLASS_INITIALIZED_METHOD;

    private static final Object UNSAFE;

    public Java8ClassDefiner(int majorVersion, MethodHandles.Lookup caller, Class<?> targetClass) {
        super(majorVersion, caller, targetClass);
    }

    @Override
    public Class<?> defineClass(byte[] classBytes) throws Exception {
        try {
            DEFINE_ANONYMOUS_CLASS_METHOD.setAccessible(true);
            return (Class<?>) DEFINE_ANONYMOUS_CLASS_METHOD.invoke(UNSAFE, targetClass, classBytes, null);
        } catch (Exception ex) {
            throw new RuntimeException("Failed to define anonymous class", ex);
        }
    }

    @Override
    public MethodHandle find(Class<?> refc, String name, MethodType type) {
        try {
            return IMPL_LOOKUP.findStatic(refc, name,
                    type);
        } catch (NoSuchMethodException | IllegalAccessException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public void ensureClassInitialized(Class<?> target) {
        try {
            ENSURE_CLASS_INITIALIZED_METHOD.invoke(UNSAFE, target);
        } catch (InvocationTargetException | IllegalAccessException ex) {
            throw new RuntimeException(ex);
        }
    }

    private static Class<?> getUnsafeClass() throws ClassNotFoundException {
        return Class.forName("sun.misc.Unsafe");
    }

    private static Object getUnsafe(Class<?> unsafeClass) {
        try {
            Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
            theUnsafeField.setAccessible(true);
            return theUnsafeField.get(null);
        } catch (IllegalAccessException | NoSuchFieldException ex) {
            throw new RuntimeException("Failed to get Unsafe instance", ex);
        }
    }

    private static Method getDefineAnonymousClassMethod(Class<?> unsafeClass) {
        try {
            return unsafeClass.getDeclaredMethod("defineAnonymousClass", Class.class, byte[].class, Object[].class);
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException("Failed to get defineAnonymousClass method", ex);
        }
    }

    private static Method getEnsureClassInitializedMethod(Class<?> unsafeClass) {
        try {
            return unsafeClass.getDeclaredMethod("ensureClassInitialized", Class.class);
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException("Failed to get defineAnonymousClass method", ex);
        }
    }

    private static final MethodHandles.Lookup getImplLookup() {
        try {
            return AccessController.doPrivileged((PrivilegedExceptionAction<MethodHandles.Lookup>) () -> {
                try {
                    Class<?> lookupClass = Class.forName("java.lang.invoke.MethodHandles$Lookup");
                    Field implLookupField = lookupClass.getDeclaredField("IMPL_LOOKUP");
                    implLookupField.setAccessible(true);
                    return (MethodHandles.Lookup) implLookupField.get(null);
                } catch (ReflectiveOperationException e) {
                    throw new RuntimeException("Failed to get IMPL_LOOKUP", e);
                }
            });
        } catch (PrivilegedActionException e) {
            throw new RuntimeException("Failed to get IMPL_LOOKUP", e);
        }
    }

    static {
        try {
            Class<?> unsafeClass = getUnsafeClass();
            UNSAFE = getUnsafe(unsafeClass);
            DEFINE_ANONYMOUS_CLASS_METHOD = getDefineAnonymousClassMethod(unsafeClass);
            ENSURE_CLASS_INITIALIZED_METHOD = getEnsureClassInitializedMethod(unsafeClass);
            IMPL_LOOKUP = getImplLookup();
        } catch (Exception ex) {
            throw new RuntimeException("Failed to initialize Java8ClassDefiner", ex);
        }
    }
}